#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <stdbool.h>
#include <ctype.h>
#include <string.h>



struct globalArgs_t
{
	bool toBinary;    // -b option, encoding
	bool toText;      // -r option, decoding
	bool isStdin;       // true when no input specified
	bool isStdout;      // true when no output specified
	char *inputFile;  // input file argument
	char *outputFile; // output file argument
} globalArgs;

static const char *optString = "i:o:brVh?";

static const struct option longOpts[] =
{
		{ "binary", no_argument, NULL, 'b' },
		{ "reverse", no_argument, NULL, 'r' },
		{ "input", required_argument, NULL, 'i' },
		{ "output", required_argument, NULL, 'o' },
		{ "version", no_argument, NULL, 'V' },
		{ "help", no_argument, NULL, 'h' },
		{ NULL, no_argument, NULL, 0 },
};

void display_usage()
{
	puts( "Usage:\n"
		"  ./BitTranslation -h\n"
		"  ./BitTranslation -V\n"
		"  ./BitTranslation [options]\n"
		"Options:\n"
		"  -h, --help    Print this information.\n"
		"  -V, --version Print version and quit.\n"
		"  -b, --binary  Generate an output file consisting of 1's and 0's.\n"
		"  -r, --reverse Generate an output file using an encoded file as input (default mode).\n"
		"  -i, --input   Path to input file (defaults to stdin).\n"
		"  -o, --output  Path to output file (defaults to stdout).\n"
		"\n"
		"Examples:\n"
		"  ./BitTranslation\n"
		"  ./BitTranslation -i - -b -o output.01\n"
		"  ./BitTranslation --reverse --input bits.01 -o decoded_text.txt\n"
		"  ./BitTranslation --version\n");


	exit( EXIT_FAILURE );
}

void display_version()
{
	puts("Bits Encoding - Version 1.0");
	exit( EXIT_SUCCESS );
}

void check_reading_error(FILE* farch_in)
{
    if (ferror(farch_in) != 0)
    {
        fprintf(stderr,"INPUT_ERROR: An error occurred when reading the input file.\n");
        exit( EXIT_FAILURE );
    }
}

bool validate_arguments()
{
	FILE* fin;
	FILE* fout;
	//Input file
	if(globalArgs.isStdin)
	{
		fprintf(stderr, "WARNING: No input file specified. Reading from stdin...\n");
		globalArgs.inputFile = NULL;
	}
	else if( (fin = fopen(globalArgs.inputFile, "r")) )
	{
	    /* Checking input file is not empty..
		fseek(fin, 0, SEEK_END);
		if(ftell(fin) == 0)
		{
			fprintf(stderr, "INPUT_ERROR: Input file is empty.\n");
			return false;
		}
		fseek(fin, 0, SEEK_SET);*/
		fclose(fin);
	}
	else{
		fprintf(stderr,"INPUT_ERROR: Failed to open input file '%s' for reading.\n", globalArgs.inputFile);
		return false;
	}

	/* --- Output File --- */
	// STD out
	if(globalArgs.isStdout)
	{
		fprintf(stderr, "WARNING: No output file specified. Writing in stdout...\n");
		globalArgs.outputFile = NULL;
		return true;
	}

	// File out
	// Warning if exists
	if( (fout = fopen(globalArgs.outputFile, "r")) ){   // Output file can be read => exists
		fprintf(stderr,"WARNING: Output file already exists. It will be overwritten!\n");
		fclose(fout);
	}

    // Check if can be written
	if(!(fout = fopen(globalArgs.outputFile, "w")))   // Output file can be read => exists
	{
		fprintf(stderr, "OUTPUT_ERROR: Failed to open file %s for writing\n", globalArgs.outputFile);
		return false;
	}
	else
    {
        fclose(fout);
        return true;
	}
}

int txt_to_bits(char* arch_text, char* arch_bits){

	FILE* farch_in;
	FILE* farch_out;
	FILE* fout_temp;
	bool isStdin = !arch_text;
	bool isStdout= !arch_bits;
	bool tempFile = isStdin && isStdout;

	if (isStdin)
        farch_in = stdin;
	else
        farch_in = fopen(arch_text, "r");

	if (isStdout)
        farch_out = stdout;
	else
        farch_out = fopen(arch_bits, "w");

	//using temp file for stdout conversion
	if(tempFile) fout_temp = fopen("stdout.temp", "w");

	int char_ascii = 0, i, potencia;
	char bits[9];

	while( ((char_ascii = fgetc(farch_in)) != EOF) ){
		for(i=0; i<8; i++){
			potencia = 1 << (7-i);
			if( char_ascii >= potencia){
				char_ascii -= potencia;
				bits[i] = '1';
			}
			else {
			    bits[i] = '0';
			}
		}
		bits[8] = '\0';
		if(tempFile){
			if (fprintf(fout_temp, "%s", bits) < 0){ //write buffer to temp
	        	fprintf(stderr, "\n--- Error on output. There was a problem while writing on output file.\n");
	    		exit( EXIT_FAILURE );
	    	}
		}
		else{
			if (fprintf(farch_out, "%s", bits) < 0){ //write directly
	        	fprintf(stderr, "\n--- Error on output. There was a problem while writing on output file.\n");
	    		exit( EXIT_FAILURE );
	    	}
		}
	}

	check_reading_error(farch_in);


	if(tempFile){ //copy from temp to stdout.
		fclose(fout_temp);
		if((fout_temp = fopen("stdout.temp", "r")) == NULL) {
        	fprintf(stderr, "\n--- Error on output. There was a problem while writing on output file.\n");
    		exit( EXIT_FAILURE );
    	}
		fprintf(stderr, "\n--- Text conversion result:\n");
		char ch;
		while( ((ch = fgetc(fout_temp)) != EOF) ){
			if (fprintf(farch_out, "%c", ch) < 0)	exit( EXIT_FAILURE );
		}
	}
	check_reading_error(farch_in);

	fclose(farch_in);
	fclose(farch_out);
	if(tempFile){
		fclose(fout_temp);
		remove("stdout.temp");
	}
	fprintf(stderr, "\n--- Text encoded successfully. ---\n");
    return EXIT_SUCCESS;
}

int bits_to_txt(char* arch_bits, char* arch_text){
	FILE* farch_in;
	FILE* farch_out;
	FILE* fout_temp;
	bool isStdin  = !arch_bits;
	bool isStdout = !arch_text;
	bool tempFile = isStdin && isStdout;

	if (isStdin)
        farch_in = stdin;
	else
        farch_in = fopen(arch_bits, "r");

	if (isStdout)
        farch_out = stdout;
	else
        farch_out = fopen(arch_text, "w");

	//using temp file for stdout conversion
	if(tempFile) fout_temp = fopen("stdout.temp", "w");

	int char_ascii = 0, i = 0, inputc;

    while( ( (inputc = fgetc(farch_in)) != EOF ) ){
		/*
		 * i: Itera en ciclos de 8, se usa para la potencia
		 * 		y cada 8 ciclos se resetea e imprime en archout
		 *  */
        // Input is not binary
        if (inputc != '0' && inputc != '1')
        {
			if (inputc == ' ' || inputc == '\n')
				continue;
            // Input has errors
            fprintf(stderr, "\nINPUT_ERROR: Binary input file should only contain 1's and 0's.\n");

            fclose(farch_in);
            fclose(farch_out);
            remove(arch_text);
            if(tempFile){
				fclose(fout_temp);
				remove("stdout.temp");
			}
            exit (EXIT_FAILURE);
        }
        // Input ok
        i++;
        int bin = inputc - '0';
        char_ascii += (bin * (1 << (8-i)));
        if (i == 8){
        	i = 0;
			if(tempFile){
				if (fprintf(fout_temp, "%c", char_ascii) < 0){ //write buffer to temp
					fprintf(stderr, "\n--- Error on output. There was a problem while writing on output file.\n");
					exit( EXIT_FAILURE );
				}
			}
			else{
				if (fprintf(farch_out, "%c", char_ascii) < 0)	{ //write directly
					fprintf(stderr, "\n--- Error on output. There was a problem while writing on output file.\n");
					exit( EXIT_FAILURE );
				}
			}
			char_ascii = 0;
        	}
	}
	check_reading_error(farch_in);

	// Input amont invalid
	if (i != 0){
		fprintf(stderr, "\nINPUT_ERROR: Binary input file has not an entire amount of bytes.\n");
		fclose(farch_in);
		fclose(farch_out);
		remove(arch_text);
        if(tempFile){
        	fclose(fout_temp);
        	remove("stdout.temp");
        }
		exit (EXIT_FAILURE);
	}

	// Output
    if(tempFile)
    {
    	//copy from temp to stdout.
    	fclose(fout_temp);
    	if( (fout_temp = fopen("stdout.temp", "r")) == NULL){
        	fprintf(stderr, "\n--- Error on output. There was a problem while writing on output file.\n");
    		exit( EXIT_FAILURE );
    	}
    	fprintf(stderr, "\n--- Binary conversion result:\n");
    	char ch;
    	while( ((ch = fgetc(fout_temp)) != EOF) ){
    		if (fprintf(farch_out, "%c", ch) < 0)	exit( EXIT_FAILURE );
    	}
    	check_reading_error(fout_temp);
    	fclose(fout_temp);
    	remove("stdout.temp");
    }

	fclose(farch_in);
	fclose(farch_out);
	fprintf(stderr, "\n--- Binary text decoded successfully. ---\n");
    return EXIT_SUCCESS;
}


int main(int argc, char* argv[])
{
	globalArgs.toBinary = false;
	globalArgs.toText = false;
	globalArgs.isStdin = true;
	globalArgs.isStdout = true;
	globalArgs.inputFile = NULL;
	globalArgs.outputFile = NULL;
	int opt = 0;
	int longIndex = 0;
	opterr = 0;
	extern char* optarg;

	opt = getopt_long( argc, argv, optString, longOpts, &longIndex );
	while( opt!= -1 )
	{
		switch( opt )
		{
			case 'b':
				globalArgs.toBinary = true;
				break;
			case 'r':
				globalArgs.toText = true;
				break;
			case 'i':
				if(strcmp(optarg, "-") == 0){
					globalArgs.isStdin = true; //Valor por defecto
				}
				else{
					globalArgs.inputFile = optarg;
					globalArgs.isStdin = false;
				}
				break;
			case 'o':
				if(strcmp(optarg, "-") == 0){
					globalArgs.isStdout = true; //Valor por defecto
				}
				else{
					globalArgs.outputFile = optarg;
					globalArgs.isStdout = false;
				}
				break;
			case 'V':
				display_version();
				break;
			case 'h':
			case '?':
				display_usage();
				break;
			default:
				/* There are no more arguments. */
				break;
		}
		opt = getopt_long( argc, argv, optString, longOpts, &longIndex );
	}

	if (!globalArgs.toText && !globalArgs.toBinary)
    {
        fprintf(stderr, "Using default-reverse action...\n");
        globalArgs.toText = true;
    }


    if(globalArgs.toBinary) fprintf(stderr, "--- Text to Binary mode ---\n");
    if(globalArgs.toText) fprintf(stderr, "--- Binary to Text mode ---\n");

    if(!validate_arguments()){
		fprintf(stderr, "\nErrors were encountered. Program is finished.\n");
		exit (EXIT_FAILURE);
	}

	if ( globalArgs.toBinary )
        return (txt_to_bits(globalArgs.inputFile, globalArgs.outputFile));

	return (bits_to_txt(globalArgs.inputFile, globalArgs.outputFile));
}
